// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Security.Cryptography.Asn1;
using System.Security.Cryptography.Pkcs;
using System.Security.Cryptography.X509Certificates;

using Microsoft.Win32.SafeHandles;

using static Interop.Crypt32;

namespace Internal.Cryptography.Pal.Windows
{
    internal sealed partial class DecryptorPalWindows : DecryptorPal
    {
        internal static DecryptorPalWindows Decode(
            ReadOnlySpan<byte> encodedMessage,
            out int version,
            out ContentInfo contentInfo,
            out AlgorithmIdentifier contentEncryptionAlgorithm,
            out X509Certificate2Collection originatorCerts,
            out CryptographicAttributeObjectCollection unprotectedAttributes
            )
        {
            SafeCryptMsgHandle? hCryptMsg = null;
            try
            {
                hCryptMsg = Interop.Crypt32.CryptMsgOpenToDecode(MsgEncodingType.All, 0, 0, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
                if (hCryptMsg == null || hCryptMsg.IsInvalid)
                    throw Marshal.GetLastPInvokeError().ToCryptographicException();

                if (!Interop.Crypt32.CryptMsgUpdate(
                    hCryptMsg,
                    ref MemoryMarshal.GetReference(encodedMessage),
                    encodedMessage.Length,
                    fFinal: true))
                {
                    throw Marshal.GetLastPInvokeError().ToCryptographicException();
                }

                CryptMsgType cryptMsgType = hCryptMsg.GetMessageType();
                if (cryptMsgType != CryptMsgType.CMSG_ENVELOPED)
                    throw ErrorCode.CRYPT_E_INVALID_MSG_TYPE.ToCryptographicException();

                version = hCryptMsg.GetVersion();

                contentInfo = hCryptMsg.GetContentInfo();

                AlgorithmIdentifierAsn contentEncryptionAlgorithmAsn;
                using (SafeHandle sh = hCryptMsg.GetMsgParamAsMemory(CryptMsgParamType.CMSG_ENVELOPE_ALGORITHM_PARAM))
                {
                    unsafe
                    {
                        CRYPT_ALGORITHM_IDENTIFIER* pCryptAlgorithmIdentifier = (CRYPT_ALGORITHM_IDENTIFIER*)(sh.DangerousGetHandle());
                        contentEncryptionAlgorithm = (*pCryptAlgorithmIdentifier).ToAlgorithmIdentifier();
                        contentEncryptionAlgorithmAsn.Algorithm = contentEncryptionAlgorithm.Oid.Value!;
                        contentEncryptionAlgorithmAsn.Parameters = (*pCryptAlgorithmIdentifier).Parameters.ToByteArray();
                    }
                }

                originatorCerts = hCryptMsg.GetOriginatorCerts();
                unprotectedAttributes = hCryptMsg.GetUnprotectedAttributes();

                RecipientInfoCollection recipientInfos = CreateRecipientInfos(hCryptMsg);
                return new DecryptorPalWindows(hCryptMsg, recipientInfos, contentEncryptionAlgorithmAsn);
            }
            catch
            {
                hCryptMsg?.Dispose();
                throw;
            }
        }
    }
}
